/*
 * BlockManager.cpp
 *
 *  Created on: Mar 22, 2010
 *      Author: Lukasz Spintzyk
 */

#include "BlockReader.h"
#include "BlockGroup.h"
#include "IOStream.h"
#include "FSException.h"

BlockManager::BlockManager(IOStream& io, SuperBlock& sb)
	:d_io(io)
	 ,d_sb(sb)
{
	d_buffer = new byte[8*1024*10];
}

BlockManager::~BlockManager()
{
	for(BlockMap::const_iterator cit = d_groups.begin(); cit!=d_groups.end(); ++cit)
	{
		delete (*cit).second;
	}
	d_groups.clear();
	delete[] d_buffer;
}

void BlockManager::getBlock(uint32_t nr, void* buffer)
{
	//uint32_t block_offset = d_groupNumber*d_sb.d_blockSize*d_sb.d_blocksPerGroup;
	uint32_t begin = nr*d_sb.d_blockSize;
	uint32_t end = begin + d_sb.d_blockSize;
	assert(begin <= end);
	d_io.readRange(buffer, begin, end);
}

void BlockManager::writeStringToBlock(uint32_t block_number, const char* string)
{
	memcpy(d_buffer, string, strlen(string)+1);
	setBlock(block_number, d_buffer);
}

void BlockManager::setBlock(uint32_t nr,const void* buffer)
{
	setBlock(nr/d_sb.d_blocksPerGroup, nr%d_sb.d_blocksPerGroup, buffer);
}

void BlockManager::getBlock(uint32_t gr, uint32_t nr, void* buffer)
{
	assert(nr < d_sb.d_blocksPerGroup);
	uint32_t begin = gr*d_sb.d_blocksPerGroup*d_sb.d_blockSize + nr*d_sb.d_blockSize;
	uint32_t end = begin + d_sb.d_blockSize;
	assert(begin <= end);
	d_io.readRange(buffer, begin, end);
}

void BlockManager::setBlock(uint32_t gr, uint32_t nr,const void* buffer)
{
	assert(nr < d_sb.d_blocksPerGroup);
	uint32_t begin_gr = gr*d_sb.d_blocksPerGroup*d_sb.d_blockSize;
	uint32_t begin = begin_gr + nr*d_sb.d_blockSize;
	uint32_t end = begin + d_sb.d_blockSize;

	d_io.writeRange(buffer, begin, end);

	BlockDesc& group = getBlockGroupDesc(gr);
	group.setBlock(nr, buffer);
	setBlockGroupDesc(group);
}

/**
 * SuperBlock is always located on file/disk/partition at byte 1024
 */

void BlockManager::readSuperBlock()
{
	uint32_t begin = 1024;

	// sizeof(SuperBlock) < 1024 and 1024 is the smallest blockSize possible
	uint32_t end = begin + 1024;
	assert(begin <= end);
	d_io.readRange(d_buffer, begin, end);
	d_sb.read(d_buffer);
}

void BlockManager::writeSuperBlock()
{
	uint32_t begin = 1024;

	// sizeof(SuperBlock) < 1024 and 1024 is the smallest blockSize possible
	uint32_t end = begin + 1024;

	d_sb.write(d_buffer);
	d_io.writeRange(d_buffer, begin, end);
}

uint32_t BlockManager::getFileSize()
{
	return d_io.getLenght();
}

BlockDesc& BlockManager::getBlockGroupDesc(uint32_t nr)
{
	// TODO Test function
	BlockMap::const_iterator find_it = d_groups.find(nr);

	if (find_it == d_groups.end())
	{
		d_groups[nr] = new BlockDesc(nr);
	}
	BlockDesc& group = *(d_groups[nr]);
	group.setGroupNumber(nr);
	assert(group.d_groupNumber == nr);

	group.setSuperBlock(d_sb);
	uint32_t begin_gr = nr*d_sb.d_blocksPerGroup*d_sb.d_blockSize;
	uint32_t end_gr = begin_gr + group.sizeInBlocks()*d_sb.d_blockSize;

	assert(begin_gr <= end_gr);
	d_io.readRange(d_buffer, begin_gr, end_gr);
	group.setSuperBlock(d_sb);
	group.readDesc(d_buffer);

	return group;
}

void BlockManager::setBlockGroupDesc(BlockDesc& group)
{
	// TODO Test function
	uint32_t nr = group.getGroupNumber();
	group.setSuperBlock(d_sb);

	uint32_t begin_gr = nr*d_sb.d_blocksPerGroup*d_sb.d_blockSize;
	uint32_t end_gr = begin_gr + group.sizeInBlocks()*d_sb.d_blockSize;

	group.writeDesc(d_buffer);

	d_io.writeRange(d_buffer, begin_gr, end_gr);
}

uint32_t BlockManager::reserveBlocksNearTo(
		uint32_t near_block,
		uint32_t num,
		std::vector< std::pair< uint32_t, uint32_t > >& reserved)
{
	// TODO Test function
	uint32_t reserved_blocks_num = 0;
	uint32_t part = num;
	uint32_t new_near = near_block;
	while((num > 0) &&
		  (d_sb.d_freeBlocks >= num)
		  )
	{
		new_near = tryReserveBlocksNearTo(near_block, part);
		if (new_near == 0) // no such big block on disk
		{
			if (part == 1)
			{
				break;
			}
			//try alloc smaller portion of blocks
			part>>=1;// div by 2
			if (part==0)
			{
				part = 1;
			}
		}
		else
		{
			reserved.push_back(std::make_pair(new_near, part));
			near_block = new_near;
			assert(num >= part);
			num -= part;
			reserved_blocks_num += part;
		}

		part = std::min(part, num);
	}

	return reserved_blocks_num;
}

void BlockManager::updateFreeBlocks(int32_t add)
{
	assert(d_sb.d_freeBlocks > 0);
	d_sb.d_freeBlocks += add;
}
/**!
 * Function try allocate consistent num of blocks
 */
uint32_t BlockManager::tryReserveBlocksNearTo(uint32_t near_block,const uint32_t num)
{
	//TODO test function
	uint32_t group = near_block / d_sb.d_blocksPerGroup;
	uint32_t after_block = near_block % d_sb.d_blocksPerGroup;
	uint32_t max_groups = d_sb.d_blocksCount/d_sb.d_blocksPerGroup;

	uint32_t retBlock = 0;
	BlockDesc& near_Group = getBlockGroupDesc(group);
	if (near_Group.tryReserveBlocks(num, retBlock, after_block))
	{
		setBlockGroupDesc(near_Group);
		updateFreeBlocks(-num);
		return retBlock + group*d_sb.d_blocksPerGroup;
	}

	for (uint32_t g = group + 1; g < max_groups; ++g)
	{
		BlockDesc& nearGroup = getBlockGroupDesc(g);
		if (nearGroup.tryReserveBlocks(num, retBlock))
		{
			setBlockGroupDesc(nearGroup);
			updateFreeBlocks(-num);
			return retBlock + g*d_sb.d_blocksPerGroup;
		}
	}

	for (uint32_t g = 0; g <= group; ++ g)
	{
		BlockDesc& nearGroup = getBlockGroupDesc(g);
		if (nearGroup.tryReserveBlocks(num, retBlock))
		{
			setBlockGroupDesc(nearGroup);
			updateFreeBlocks(-num);
			return retBlock + g*d_sb.d_blocksPerGroup;
		}
	}

	return 0;
}

uint32_t BlockManager::reserveFreeInode()
{
	BlockDesc* group = 0;
	uint32_t groups_count = d_sb.d_groupNum;
	uint32_t node = 0;
	for (BlockMap_it it = d_groups.begin(); it != d_groups.end(); ++it)
	{
		node = (*it).second->getFreeInode();
		if ( node )
		{
			group = (*it).second;
			group->setInode(node, true);
			setBlockGroupDesc(*group);
			return d_sb.d_inodesPerGroup*group->d_groupNumber + node;
		}

	}


	for (uint32_t g = 0; g < groups_count; ++g)
	{
		group = &getBlockGroupDesc(g);
		node = group->getFreeInode();
		if ( node )
		{
			group->setInode(node, true);
			setBlockGroupDesc(*group);
			return d_sb.d_inodesPerGroup*group->d_groupNumber + node;
		}
	}

	throw FSException(ENFILE ,"No free inodes");
	return 0;
}

void BlockManager::freeBlock(uint32_t blockNumber)
{
	uint32_t groupNum = blockNumber/d_sb.d_blocksPerGroup;
	uint32_t blockBit = blockNumber%d_sb.d_blocksPerGroup;

	BlockDesc& group = getBlockGroupDesc(groupNum);
	assert(group.d_blocksBitMap.test(blockBit) == true);
	group.d_blocksBitMap.set(blockBit, false);
	setBlockGroupDesc(group);
}
